<?

/**
 * 
 * @version 0.3 beta
 * @author Dmitriy Chechotkin
 * 
 */
define('ACL_READ', 1);
define('ACL_WRITE', 2);
define('ACL_READ_CHIILDS', 4);
define('ACL_ADD_CHIILDS', 8);
define('ACL_WRITE_CHIILDS', 16);
define('ACL_SHARE', 32);
define('ACL_DELETE_CHILDS', 64);
define('ACL_DELETE', 128);

class DataSource {


	/**
	 * ID текущего пользователя БД
	 * @var integer
	 */
	public static $userid;
	/**
	 * Информация о текущем пользователе БД
	 * @var RecordSet
	 */
	public static $user;
	
	/**
	 * Флаг инициализации библиотеки
	 * @var boolean
	 */
	private static $inited;

	
	/**
	 * Пытается воостановить сессию пользователя БД по информации из кукисов. 
	 * (используется при длительном отсутствии запросов к серверу)
	 * @return boolean
	 */
	static function restore_session() {
		Log::put('Trying to restore session. User: '.@$_SESSION['user'].', cookie: '.@$_COOKIE['JMPUID'], 'session.log');
		$c = @$_COOKIE['JMPUID'];
		// CORE LEVEL REQUESTS:
			
		// searching user_id by auth
		$query = "SELECT `parent_id` as `user_id` ".
				"FROM `relations`, `auth` ".
				"WHERE relations.child = 'auth' ".
					"AND relations.child_id = auth.id ".
					"AND auth.ipmatch = '1' ".
					"AND auth.ip LIKE '".mysqli_real_escape_string($_SESSION['pconnect'], @$_SERVER['REMOTE_ADDR'])."' ".
					"AND auth.cookie = '".mysqli_real_escape_string($_SESSION['pconnect'], @$c)."'";
		$query .= "LIMIT 1";
		$res = @mysqli_fetch_array(@mysqli_query($_SESSION['pconnect'], $query));
		if (!$res || mysqli_affected_rows($_SESSION['pconnect']) == 0) {
			Log::put('No users selected...', 'session.log');
			return false;
		}
		self::$userid = $res['user_id'];
		Log::put('Selected user#'.$res['user_id'], 'session.log');
		try{
			self::$user = self::select('group,$user.', 'user.id = self.id');
			@define('USERMODE_USER', 1);
		} catch (Exception $e) {
			die($e->getMessage());
		}
		self::$user = self::$user->current()->user;
		$_SESSION['user'] = self::$userid;
		Log::put('User name: '.self::$user->name, 'session.log');

		return true;

	}

	/**
	 * Пытается продолжить сессию пользователя, восстанавливая её из $_SESSION 
	 * @return boolean
	 */
	static function continue_session() {
		Log::put('Trying to continue session. User: '.@$_SESSION['user'].', cookie: '.@$_COOKIE['JMPUID'], 'session.log');
		if (!defined('SQL_PREFIX')) {
			$cfg = new Config('sql');
			define('SQL_PREFIX', $cfg->prefix);
			$_SESSION['pconnect'] = @mysqli_connect($cfg->host, $cfg->user, $cfg->password, $cfg->database) or Log::warning(mysqli_connect_error(), QConst::X_DB_FAULT, 'db.log');
			if (!self::check_conn()) {
				@define('USER_GUEST', 1);
				self::fake_user();
				return;
			}
			if(mysqli_errno($_SESSION['pconnect'])) die(__LINE__.': USE failed: '.mysqli_error($_SESSION['pconnect']));;
		}
		if (!self::check_conn()) {
			@define('USER_GUEST', 1);
			self::fake_user();
			return;
		}

		mysqli_query($_SESSION['pconnect'], "SET NAMES UTF8") or Log::fatal('names failed:<br/>'.mysqli_error($_SESSION['pconnect']));
		if (self::restore_session()) return true;

		$user = @$_SESSION['user'];
		if (!$user) Log::fatal('Unable to restore session: no userid', 'session.log');
		self::$userid = $user;
			
		$user = self::select('self,group,$user, $auth.', 'user.id = self.id');
		if ($user->count() == 0) {
			unset($_SESSION['user']);
			Log::fatal('Unable to restore session', 'session.log');
		}
		$mix = $user->current();
		self::$user =$mix->user;
		if (!$mix->user->reg) {
			@define('USER_GUEST', 1);
		} else @define('USERMODE_USER', 1);
		Log::put('session restored', 'session.log');
		return true;
	}

	/**
	 * Хак для быстрой проверки логина на занятость
	 * @param $login
	 * @return boolean
	 */
	static function check_login($login) {
		$login = mysqli_real_escape_string($_SESSION['pconnect'], $login);
		$query = "SELECT COUNT(*) as list FROM `auth` WHERE `login` = '$login'";
		$res = mysqli_query($_SESSION['pconnect'], $query) or die("$query: ".mysqli_error($_SESSION['pconnect']));
		$res = @mysqli_fetch_array($res);
		if ($res['list']) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Создаёт гостевой аккаунт для указанного ip-адреса.
	 * Возвращает id созданного пользователя
	 * @param $ip
	 * @return integer
	 */
	static function create_guest($ip) {
		if (!self::check_conn()) return false;
		// creating user
		$query = "INSERT INTO `user` (`id`, `name`, `reg`) VALUES (0, '".mysqli_real_escape_string($_SESSION['pconnect'], $ip)."', 0)";
		mysqli_query($_SESSION['pconnect'], $query) or die("$query: ".mysqli_error($_SESSION['pconnect']));
		$user = mysqli_insert_id($_SESSION['pconnect']);
			
		// creating auth
		$query = "INSERT INTO `auth` (`id`, `ip`, `ipmatch`) VALUES (0, '".mysqli_real_escape_string($_SESSION['pconnect'], $ip)."', '1')";
		mysqli_query($_SESSION['pconnect'], $query) or die("$query: ".mysqli_error($_SESSION['pconnect']));
		$auth = mysqli_insert_id($_SESSION['pconnect']);

			
		// searching for Guest group
		$query = "SELECT `id` FROM `group` WHERE `name` = 'Guest'";
		$res = mysqli_query($_SESSION['pconnect'], $query) or die("$query: ".mysqli_error($_SESSION['pconnect']));
		$res = @mysqli_fetch_array($res);
		$gg = $res['id'];
		if (!$gg) throw new Exception('Unable to find group with name "Guest"!');

		// creating links
		$query = "INSERT INTO `relations` ".
				"(`id`, `parent`, `parent_id`, `child`, `child_id`, `acl`) ".
				"VALUES ".
					"(0, 'cercea', 1, 'user', $user, 5),".
					"(0, 'user', $user, 'user', $user, 255),".
					"(0, 'user', $user, 'cercea', 1, 1),".
					"(0, 'group', $gg, 'user', $user, 1),".
					"(0, 'user', $user, 'group', $gg, 5),".
					"(0, 'user', $user, 'auth', $auth, 3)";
		if(!mysqli_query($_SESSION['pconnect'], $query)) throw new Exception("Unable to create relation cercea(1)->group($gg)&lt;->user($user)->auth($auth) with query [$query]");
		return $user;
	}

	/**
	 * Преобразует гостевую запись в пользовательскую
	 * @param $login
	 * @param $password
	 * @param $name
	 * @return RecordSet
	 */
	static function guest2user($login, $password, $name)
	{
		if (!self::check_conn()) return false;
		$login = addslashes($login);
		$password = addslashes($password);
		$query = "SELECT COUNT(*) as count FROM `auth` WHERE `login`='$login' AND `password` = '$password'";
		$chk = mysqli_fetch_array(mysqli_query($_SESSION['pconnect'], $query));
		if (@$chk['count'] == 0) {
			$auth = self::select('self, $auth.');
			$auth = $auth->item(0);
			$auth = $auth->auth;
			$query = "UPDATE `auth` SET `login` = '$login', `password` = '$password', `ipmatch` = 0 WHERE `id` = ".$auth->id;
			mysqli_query($_SESSION['pconnect'], $query);
			$query = "UPDATE `relations` SET `acl` = '255' WHERE `child` = 'auth' AND `child_id` = ".$auth->id;
			mysqli_query($_SESSION['pconnect'], $query);
			$query = "DELETE FROM `relations` WHERE `parent` = 'group' AND `child` = 'user' AND `child_id` = ".self::$user->id;
			mysqli_query($_SESSION['pconnect'], $query);
			$query = "SELECT * FROM `group` WHERE `name` = 'User'";
			$res = mysqli_query($_SESSION['pconnect'], $query);
			if (!$res) die( mysqli_error($_SESSION['pconnect'])) ;
			$res = mysqli_fetch_array($res);

			$query = "INSERT INTO `relations` (`acl`, `parent`, `parent_id`, `child`, `child_id`) VALUES ('13', 'user', '".self::$userid."', 'group', '".$res['id']."'), ('13', 'group', '".$res['id']."', 'user', '".self::$userid."'), ('13', 'cercea', '1', 'user', '".self::$userid."')";
			$res = mysqli_query($_SESSION['pconnect'], $query);
			if (!$res) die( $query.' :::::: '.mysqli_error($_SESSION['pconnect'])) ;
		} else {
			Log::fatal('There is already registered user with login "'.$login.'"!', 'reg.log');
		}
		$acfg = new Config('auth');
		$q = "UPDATE `user` SET `name` = '$name', `reg` = 1, `email` = '$login' ";
		if ($acfg->allow_registrations) {
			self::$user->approved = 1;
			$q .= ', `approved` = 1';
		}
		$q .= " WHERE `id` = ".self::$user->id;
		mysqli_query($_SESSION['pconnect'], $q);
		self::$user = DataSource::selectRecord('self, group, $user.', 'user.id = self.id');
		session_destroy();
		session_start();
		return self::$user;
	}

	/**
	 * Авторизовывает гостя по его ip-адресу
	 * @param $ip
	 * @return boolean
	 */
	static function guest($ip) {
		if (!self::check_conn()) return false;
		@define('USER_GUEST', 1);
		if (!defined('SQL_PREFIX')) {
			$cfg = new Config('sql');
			if (!$cfg->prefix) {
				$cfg->prefix = 'no';
				$cfg->save();
			}
			define('SQL_PREFIX', $cfg->prefix);
			$_SESSION['pconnect'] = mysqli_connect($cfg->host, $cfg->user, $cfg->password);
			mysqli_select_db($_SESSION['pconnect'], $cfg->database) or die("Database selection: ".mysqli_error($_SESSION['pconnect']));
		}
		mysqli_query($_SESSION['pconnect'], "SET NAMES UTF8") or die('names failed:<br/>'.mysqli_error($_SESSION['pconnect']));
			
		// CORE LEVEL REQUESTS:
			
		// searching user_id by auth
		$query = "SELECT `parent_id` as `user_id` ".
				"FROM `user`, `relations`, `auth` ".
				"WHERE relations.child = 'auth' ".
					"AND relations.child_id = auth.id ".
					"AND auth.ip LIKE '".mysqli_real_escape_string($_SESSION['pconnect'], $ip)."' ".
					"AND user.id = relations.parent_id ".
					"AND user.reg = 0 ";
		$query .= "LIMIT 1";
		$res = @mysqli_fetch_array(@mysqli_query($_SESSION['pconnect'], $query));
		if (!$res) {
			//die ('ng: '.$query);
			self::$userid = self::create_guest($ip);
		} else {
			self::$userid = $res['user_id'];
		}
		try{
			self::$user = self::select('group,$user.', 'user.id = self.id');
		} catch (Exception $e) {
			die(__LINE__.': unable make guest login for guest#'.self::$userid);
		}
		self::$user = self::$user->current();
		$_SESSION['user'] = self::$userid;
		return self::continue_session();
	}

	/**
	 * Авторизует пользователя по логину, паролю и (опционально) ip-адресу
	 * @param $login
	 * @param $password
	 * @param $ip
	 * @return boolean
	 */
	static function me($login, $password, $ip = '%') {
		Log::put('Trying to init with login `'.$login.'`, password `'.$password.'` and ip `'.$ip.'`', 'auth.log');
		if (!defined('SQL_PREFIX')) {
			Log::put('Connecting to mysql...', 'db.log');
			$cfg = new Config('sql');
			define('SQL_PREFIX', $cfg->prefix);
			$_SESSION['pconnect'] = mysqli_connect($cfg->host, $cfg->user, $cfg->password);
			if (!self::check_conn()) {
				Log::put('Connection to mysql failed, cause: '.mysqli_error(), 'db.log');
				return false;
			}
			mysqli_select_db($_SESSION['pconnect'], $cfg->database) or die('USE failed:<br/>'.mysqli_error($_SESSION['pconnect']));
		}
		if (!self::check_conn()) return false;
		Log::put('Mysql connected.', 'db.log');
		mysqli_query($_SESSION['pconnect'], "SET NAMES UTF8") or die('names failed:<br/>'.mysqli_error($_SESSION['pconnect']));
			
		// CORE LEVEL REQUESTS:
			
		// searching user_id by auth
		if ($login && $password) {
			$query = "SELECT `parent_id` as `user_id` ".
				"FROM `relations`, `auth` ".
				"WHERE relations.child = 'auth' ".
					"AND relations.child_id = auth.id ".
					"AND auth.login = '".mysqli_real_escape_string($_SESSION['pconnect'], $login)."' ".
					"AND auth.password = '".mysqli_real_escape_string($_SESSION['pconnect'], $password)."' ";
					"AND auth.ipmatch = '0' ".
					"AND auth.cookie = ''";
		} else {
			$query = "SELECT `parent_id` as `user_id` ".
				"FROM `relations`, `auth` ".
				"WHERE relations.child = 'auth' ".
					"AND auth.cookie = ''";
					"AND auth.ip LIKE '".mysqli_real_escape_string($_SESSION['pconnect'], $ip)."' ";
		}
		$query .= "LIMIT 1";

		Log::put("login request: $query", 'auth.log');
		$res = @mysqli_fetch_array(@mysqli_query($_SESSION['pconnect'], $query));
		if (!$res || mysqli_affected_rows($_SESSION['pconnect']) == 0) {
			Log::put('No users selected... $res: '.$res, 'auth.log');
			return false;
		}
		self::$userid = $res['user_id'];
		Log::put('Selected user#'.$res['user_id'], 'auth.log');
		self::$user = self::select('group,$user.', 'user.id = self.id');
		@define('USERMODE_USER', 1);
		self::$user = self::$user->current()->user;
		$_SESSION['user'] = self::$userid;
		Log::put('User name: '.self::$user->name, 'auth.log');
		self::cookie();
		return true;
	}


	/**
	 * Выбирает запись из БД
	 *
	 * since CSDA6 can be called as DataSource::select($rql_relation, $rql_where = '', $rql_order = '', $rql_limit = '', $rql_group);
	 *
	 * @param unknown_type $types
	 * @param unknown_type $relation
	 * @param unknown_type $where
	 * @param unknown_type $limit
	 * @param unknown_type $order
	 * @return RecordSet
	 */
	static function select($types, $relation = '', $where = '', $limit = '', $order = '', $group = '') {
		if (!self::check_conn()) return false;
		Log::put('DS searchig: '.$types.'~'.$relation, 'csda.log');
		if (stristr($types, '$')) {
			$rql = self::RQL2SQL($types, $relation);
			$query = $rql['sql'];
			if ($where) {
				$query['order'] = addslashes($where);
			}
			if ($order) {
				$query['group'] = addslashes($order);
			}
			if ($limit) {
				$query['limit'] = addslashes($limit);
			}
				
			$query = self::mk_sql($query);
			$types = (is_array($rql['types'])) ? implode(', ', $rql['types']) : $rql['types'];
		} else $query = self::makeSelectQuery($types, $relation, $where, $limit, $order);
		$res = mysqli_query($_SESSION['pconnect'], $query);
		if (!$res) Log::fatal("error in query [$query]: ".mysqli_error($_SESSION['pconnect']));
		try{
			$ret = new RecordSet($res, $types, mysqli_affected_rows($_SESSION['pconnect']));
		} catch (Exception $e) {
			Log::fatal($e->getMessage()."; Query: \r\n\r\n".$query."\r\n\r\n");
		}
		$res = $ret->read_array();
		foreach ($res as $mixrec) {
			$types = $mixrec->get_types();
			foreach ($types as $type) {
				$rec = $mixrec->$type;
				$e = new Event(DBEVENT::AFTER_SELECT, array('#rec'=>$rec));
				if ($e->cancelled)	throw new Exception('DBEVENT::AFTER_SELECT event cancelled for record '.$rec->get_type().'['.$rec->id.'] at handler for '.$e->handler[0].'['.$e->handler[1].']');
			}
		}
		if(mysqli_error($_SESSION['pconnect'])) die($query.':<br/>'.mysqli_error($_SESSION['pconnect']));
		return $ret;
	}

	/**
	 * Возвращает количество выбираемых запросом записей
	 * 
	 * @param $path
	 * @param $filter
	 * @return integer
	 */
	static function count($path, $filter) {
		if (!self::check_conn()) return false;
		if (!$filter) $filter = 'TRUE';
		Log::put('DS counting: '.$path.'~'.$filter, 'csda.log');
		$rql = self::RQL2SQL($path, $filter);
		$query = $rql['sql'];
		$query['select'] = preg_replace('/^.*\$(\w+).*$/sim', 'COUNT(`$1`.`id`)', $path);
		$query = self::mk_sql($query);
		$types = (is_array($rql['types'])) ? implode(', ', $rql['types']) : $rql['types'];

		$res = mysqli_query($_SESSION['pconnect'], $query);
		if (!$res) Log::fatal("error in query [$query]: ".mysqli_error($_SESSION['pconnect']));

		$res = mysqli_fetch_row($res);

		return $res[0];
	}

	/**
	 * Выбирает одну (первую) запись.
	 *
	 * @param string $path
	 * @param string $filter
	 * @return Record
	 */
	static function selectRecord($path, $filter = '', $order = '')
	{
		if (!self::check_conn()) return false;
		$rql = self::RQL2SQL($path, $filter);
		$query = $rql['sql'];
		if (count($rql['types']) > 1) throw new Exception('DataSource::selectRecord cannot be used to select mixed types.');
		$query['order'] = addslashes($order);
		$query['limit'] = 1;
		$query = self::mk_sql($query);
			
		$res = mysqli_query($_SESSION['pconnect'], $query);
		if (!$res) Log::fatal("error in query [".($query)."]: ".(mysqli_error($_SESSION['pconnect'])));

		$res = mysqli_fetch_array($res, MYSQLI_ASSOC);
		if (!$res['id']) throw new Exception('No result in query '.$query);
		$res['acl'] = $res['###'];
		$ret = new Record($res, $rql['types'][0]);
			
		return $ret;
	}

	/**
	 * Внутренняя функция, доступная для использования в других модулях.
	 * Собирает SQL-запрос из переданных параметров.
	 *  
	 * @param $types
	 * @param $relation
	 * @param $where
	 * @param $limit
	 * @param $order
	 * @return string
	 */
	static function makeSelectQuery($types, $relation, $where = '', $limit = '', $order = '') {
		if (!self::check_conn()) return false;
		if (!strstr($relation, 'self.')) $relation = 'self.'.$relation;
		$select = '';
		$from = '';
		$acl_string = '';
		$wh = ' TRUE ';
		$pel = false;
		foreach (explode('.', $relation) as $rt) {
			if ($from)  {
				$from .= ", relations as `{$rt}_rel`";
				if (@strstr($types, $rt)) $from .= ", `$rt`";
			} elseif ($rt != 'self') $from = "`$rt`";
			else $from = "`user` as `self`";
			if (@$pel) {
				$acl_string = "`{$rt}_rel`.`acl` ";
				//else $acl_string .= "& `{$rt}_rel`.`acl` ";
				@$acl += ACL_READ_CHIILDS;
				if ($acl && strlen($wh) > 6) $wh .= " AND {$pel}_rel.acl & $acl = $acl ";
				$acl = 0;
				$wh .= 'AND ';
				if (@stristr($where, $rt) || @stristr($types, $rt)) $acl += ACL_READ;
				$wh .= "(`{$rt}_rel`.`parent` = ".
				(($pel == 'self') ? "'user' AND `{$rt}_rel`.`parent_id` = `self`.`id` ": "'$pel'").
				(($pel != 'self') ? " and `{$rt}_rel`.`parent_id` = `{$pel}_rel`.child_id " :'').
						" and `{$rt}_rel`.`child` = '$rt')";
				//" and `{$rt}_rel`.id = `{$rt}_rel`.child_id) ";
			}
			$pel = $rt;
			if (@strstr($types, $rt)) {
				$wh .= " AND `$rt`.id = `{$rt}_rel`.`child_id` ";
				if ($select) $select .= ', ';
				$select .= "$acl_string as `###`, `$rt`.*";
			}
		}
		if ($acl) $wh .= " AND `{$pel}_rel`.acl & $acl = $acl ";
		@$query .= "SELECT DISTINCT $select FROM $from WHERE $wh";
		if ($where) $query .= 'and ('.$where.') ';
		$query .= 'and self.id = '.self::$userid.' ';
		if ($limit) $query .= " LIMIT $limit ";
		if ($order) $query .= "ORDER BY $order";
		return $query;
	}

	/**
	 * Возвращает все связи для записи с типом $rt и номером $id 
	 * @param $rt
	 * @param $id
	 * @return array
	 */
	static function Relations($rt, $id) {
		if (!self::check_conn()) return false;
		// loading parents
		$ret['parents'] = self::parents($rt, $id);
			
		// loading childs
		$ret['childs'] = self::childs($rt, $id);
			
		return $ret;
	}

	/**
	 * Возвращает все связи к родителям от записи типа $rt с номером $id
	 * @param $rt
	 * @param $id
	 * @return array
	 */
	static function parents($rt, $id) {
		if (!self::check_conn()) return false;
		$query = "SELECT * FROM `relations` WHERE `child` = '$rt' and `child_id` = '$id'";
		return self::sql2arr(mysqli_query($_SESSION['pconnect'], $query));
	}

	/**
	 * Возвращает все связи к детям от записи типа $rt с номером $id
	 * @param $rt
	 * @param $id
	 * @return array
	 */
		static function childs($rt, $id) {
		if (!self::check_conn()) return false;
		$query = "SELECT * FROM `relations` WHERE `parent` = '$rt' and `parent_id` = '$id'";
		return self::sql2arr(mysqli_query($_SESSION['pconnect'], $query));
	}

	/**
	 * Возвращает результат простого запроса к БД в виде массива.
	 * @param $res
	 * @return array
	 */
	private static function sql2arr($res) {
		if (!self::check_conn()) return false;
		$ret = array();
		while($line = mysqli_fetch_array($res)) {$ret[]=$line;};
		return $ret;
	}

	/**
	 * Обновляет запись в БД. 
	 * @param Record $rec
	 * @return null
	 */
	static function saveRecord(Record $rec) {
		if (!self::check_conn()) return false;
		$fields = $rec->getDef();
		$type = $rec->get_type();
		// checking privileges
		/*			$sql = self::RQL2SQL($path, $type.'.id = '.$rec->id(), 	2);
			$res = mysqli_fetch_array(mysqli_query($_SESSION['pconnect'], $sql));
			if ($res['###'] != 255) throw new Exception('unable to save record ['.$type.':'.$rec->id().']: access denied.', 1012);
			*/
		$vals = '';
		foreach ($fields as $k=>$v) {
			if ($k == '###') continue;
			if ($vals) $vals .= ', ';
			$vals .= '`'.$v.'` = "'.$rec->$v.'"';
		}
		$query = 'UPDATE `'.$type.'` SET '.$vals.' WHERE `id` = '.$rec->id();
		if (!mysqli_query($_SESSION['pconnect'], $query)) throw new Exception($query.' :: '.mysqli_error($_SESSION['pconnect']));
	}


	/**
	 * Создаёт дочернюю по отношению к указанной запись типа $type и возвращает её. 
	 * 
	 * @param $type
	 * @param Record $parent
	 * @return Record
	 */
	static function createRecord($type,Record $parent) {
		if (!self::check_conn()) return false;
		$prt = $parent->get_type();
		$pid = $parent->id();
		$query = "INSERT INTO `$type` (id) VALUES (0)";
		mysqli_query($_SESSION['pconnect'], $query) or die ($query.':'.mysqli_error($_SESSION['pconnect']));
		$rid = mysqli_insert_id($_SESSION['pconnect']);
		$res = mysqli_fetch_array(mysqli_query($_SESSION['pconnect'], "SELECT * FROM `$type` WHERE `id` = $rid"), MYSQL_ASSOC);
		$res['acl'] = 255;
		$rec = new Record($res, $type);
		self::LinkRecords($parent, $rec, 255);
		return $rec;
	}
	
	/**
	 * 
	 * Используется только при установке, добавляет запись к корневому элементу БД.
	 * @param $type
	 * @return Record
	 */
	public function __addRecordToRoot($type) {
		if (!self::check_conn()) return false;
		$parent=new Record(mysqli_query($_SESSION['pconnect'], "SELECT * from cercea where `sercea_name` = 'root' limit 1"), 'cercea');
		return $this->createRecord($parent, $type);
	}

	/**
	 * Добавляет $rec2 к $rec1 в качестве потомка и устанавливает права $rec1 относительно $rec2, равнями $flags.
	 * @param Record $rec1
	 * @param Record $rec2
	 * @param $flags
	 * @return null
	 */
	public function LinkRecords(Record $rec1, Record $rec2, $flags = 255) {
		if (!self::check_conn()) return false;
		if (!($rec1->acl & 8)) throw new Exception('Unable to link childs for this record!');
		if (strstr($rec1->get_type(), ',') || strstr($rec2->get_type(), ',') || is_array($rec1->get_type()) || is_array($rec2->get_type())) throw new Exception('Unable to link mixed records!');
		$q = "INSERT INTO `relations` (acl, parent, parent_id, child, child_id) VALUES ('$flags', '".$rec1->get_type()."', '".$rec1->id()."', '{$rec2->get_type()}', ".$rec2->id().")";
		mysqli_query($_SESSION['pconnect'], $q) or die($q." \r\n\r\n ".mysqli_error($_SESSION['pconnect']));
	}

	/**
	 * Удаляет $rec2 из потомков $rec1.
	 * @param Record $rec1
	 * @param Record $rec2
	 * @return null
	 */
	public function UnlinkRecords(Record $rec1, Record $rec2) {
		if (!self::check_conn()) return false;
		if (strstr($rec1->get_type(), ',') || strstr($rec2->get_type(), ',')) throw new Exception('Unable to unlink mixed records!');
		if (!$this->IsOwner($rec2)) throw new Exception('Unable to unlink not owned record!');
		mysqli_query($_SESSION['pconnect'], "DELETE FROM `relations` WHERE parent_type = '{$rec1->get_type()}' AND parent_id = ".$rec1->__get($rec1->get_type().'_id')." AND child_type = '{$rec2->get_type()}' AND child_id = ".$rec2->__get($rec2->get_type().'_id').")");
	}

	/**
	 * @deprecated
	 * @param Record $rec1
	 * @param Record $rec2
	 * @return unknown_type
	 */
	private function newLink(Record $rec1, Record $rec2) {
		if (!self::check_conn()) return false;
		if (strstr($rec1->get_type(), ',') || strstr($rec2->get_type(), ',')) throw new Exception('Unable to link mixed records!');
		mysqli_query($_SESSION['pconnect'], "INSERT INTO `relations` (parent_type, parent_id, child_type, child_id) VALUES ('{$rec1->get_type()}', ".$rec1->__get($rec1->get_type().'_id').", '{$rec2->get_type()}', ".$rec2->__get($rec2->get_type().'_id').")", $_SESSION['pconnect']);
	}

	/**
	 * РАБОТАЕТ НЕКОРРЕКТНО
	 * @ignore
	 * @todo Добавить поддержку непрямых связей
	 * @deprecated
	 * @param Record $rec
	 * @return boolean
	 */
	public function __CanWrite(Record $rec) {
		if (!self::check_conn()) return false;
		$rt = $rec->get_type();
		$rid = $rec->get_id();
		$query = "SELECT count(*) FROM `relations` WHERE `parent_type` = 'user' AND `parent_id` = '{$_SESSION['user']['id']}' AND `child_type` = '$rt' AND `child_id` = '$rid' AND (NOT `relation_type` = NULL)";
		$res = @mysqli_fetch_array(mysqli_query($_SESSION['pconnect'], $query));
		if ($res) return true;
		else return false;
	}

	/**
	 * РАБОТАЕТ НЕКОРРЕКТНО
	 * @ignore
	 * @deprecated 
	 * @todo добавить поддержку непрямых связей
	 * @param Record $rec
	 * @return unknown_type
	 */
	public function __CanShare(Record $rec) {
		if (!self::check_conn()) return false;
		$rt = $rec->get_type();
		$rid = $rec->get_id();
		$query = "SELECT count(*) FROM `relations` WHERE `parent_type` = 'user' AND `parent_id` = '{$_SESSION['user']['id']}' AND `child_type` = '$rt' AND `child_id` = '$rid' AND (`relation_type` = 'sw')";
		$res = @mysqli_fetch_array(mysqli_query($_SESSION['pconnect'], $query));
		if ($res) return true;
		else return false;
	}

	/**
	 * НЕ РАБОТАЕТ
	 * 
	 * @deprecated
	 * @see DataSource::LinkRecords
	 */
	public function __grantPublicRead(Record $rec, $minPriv) {
		if (!self::check_conn()) return false;
		$access = $this->getPublicAccesses($minPriv, 'r', 1);
		$this->newLink($access->item(0), $rec);
	}

	/**
	 * НЕ РАБОТАЕТ
	 * 
	 * @deprecated
	 * @see DataSource::LinkRecords
	 */
	public function __grantPublicWrite(Record $rec, $minPriv) {
		if (!self::check_conn()) return false;
		$access = $this->getPublicAccesses($minPriv, 'w', 1);
		$this->newLink($access->item(0), $rec);
	}

	/**
	 * НЕ РАБОТАЕТ
	 * 
	 * @deprecated
	 * @see DataSource::LinkRecords
	 */
		private function __getPublicAccesses($priv, $type, $limit=0) {
		if (!self::check_conn()) return false;
		$priv += -100;
		if ($priv > 0 || $priv < -100) throw new Exception("Invalid privileges level!");
		$query = "SELECT * FROM `access` WHERE (NOT `access_id` < $priv) && `access_id` < 1";
		if ($limit) $query .= " LIMIT $limit";
		if(mysqli_affected_rows($_SESSION['pconnect']) == 0) return array();
		$ret = new RecordSet(mysqli_query($_SESSION['pconnect'], $query), 'access');
		return $ret->getDef();
	}

	/**
	 * Не доделано.
	 * @deprecated
	 * @param $type
	 * @return unknown_type
	 */
	static function getTypePreview($type) {
		if (!self::check_conn()) return false;
		$query = "SELECT `preview-fields` FROM `type-info` WHERE `type-name` = '".mysqli_escape_string($_SESSION['pconnect'], $type)."'";
		$res = mysqli_fetch_array(mysqli_query($_SESSION['pconnect'], $query));
		return $res['preview-fields'];
	}

	/**
	 * Часть парсера RQL 
	 * @param $cbrdata
	 * @return unknown_type
	 */
	private static function RQL_CBR2ARR(&$cbrdata) {
		$res = array ();
		$res['els'] = array (1);
		$res['sel'] = array ();
		//$x = $start;
		while (!in_array($cbrdata[4][0], array(';','.')) && count($cbrdata[4])) {
			$rem = array_shift($cbrdata[1]);
			$sel = array_shift($cbrdata[2]);
			$pel = array_shift($cbrdata[3]);
			$rel = array_shift($cbrdata[4]);
			if($sel) {
				$assc = self::ascheck(trim($pel));
				$res['sel'][] = $assc[1];
			}
			if (strstr($rel, '<')) {
				$cell[] = (bool) strstr($rel, '&');
				while ($cbrdata[1][0] != '>') {
					$rec = self::RQL_CBR2ARR($cbrdata);
					$cell[] = $rec['els'];
					$res['sel'] = array_merge($res['sel'], $rec['sel']);
				}
			}

			$res['els'][] = $pel;
			if (@$cell) $res['els'][] = $cell;
		}
		$res['els'][] = $cbrdata[3][0];
		if ($cbrdata[4][0] == ';') $res['els'][0] = 0;
		$rem = array_shift($cbrdata[1]);
		$sel = array_shift($cbrdata[2]);
		$pel = array_shift($cbrdata[3]);
		$rel = array_shift($cbrdata[4]);
		if($sel) {
			$res['sel'][] = trim($pel);
		}
		return $res;
	}

	/**
	 * Часть парсера RQL
	 * @param $arr
	 * @param $op
	 * @param $wh
	 * @return unknown_type
	 */
	private static function RQL_ARR2SQL($arr, $op = 1, $wh = '') {
		$res = array(
				'f' => array(),
				'w' => array()
		);
			
		$GLOBALS['RQL']['prev_relation_id'] = false;
			
		$sel = $arr['sel'];
		unset($arr['sel']);
		if (key_exists('els', $arr)) {
			$arr = $arr['els'];
		}


			
		for ($x = 2; $x < count($arr); $x++) {
			$parent = $arr[$x-1];
			$child = $arr[$x];

			if (!is_array($child) && !is_array($parent)) {
				// checking for «ass»
				$parent = self::ascheck($parent);
				$child = self::ascheck($child);
				$preq = (false !== array_search($parent[1], $sel));
				$preq = $preq || (false !== @strstr($wh, $parent[1]));
				$preq = $preq || ($parent[0] != $parent[1]);
				$creq = (false !== array_search($child[1], $sel));
				$creq = $creq || (false !== @strstr($wh, $child[1]));
				$creq = $creq || ($child[0] != $child[1]);
				$rel = self::RQL_relation2SQL($parent, $child, $op, $preq, $creq);
				$res['f'] = array_merge($res['f'], $rel['f']);
				$res['w'][] = $rel['w'];
			} elseif (is_array($child) && !is_array($parent)) {
				$branches['w'] = array();
				for ($y = 1;$y < count($child); $y++) {
					$cflag = $child[$y][0];
					$child[$y][0] = $parent;
					array_unshift($child[$y], $cflag);
					if (!$cflag) {
						$child[$y][] = $arr[$x+1];
					}
					$child[$y]['sel'] = $sel;
						
					$branch = self::RQL_ARR2SQL($child[$y]);
					$res['f'] = array_merge($res['f'], $branch['f']);
					if (trim($branch['w'])) $branches['w'][] = $branch['w'];
				}
				if ($child[0] && count($branches['w']) > 0) {
					$res['w'][] = '('.implode(' AND ', $branches['w']).')';
				} elseif (count($branches['w']) > 0) {
					$res['w'][] = '('.implode(' OR ', $branches['w']).')';
				}
			} elseif (is_array($parent)) {
				continue;
			} else {
				throw new Exception('Can\'t make relation between two branches arrays');
			}
		}
			
		$res['f'] = array_unique($res['f']);
		//$res['f'] = implode(', ', $res['f']);
		$res['w'] = implode(' AND ', $res['w']);
			
		return $res;
	}

	/**
	 * Часть парсера RQL
	 * @param $parent
	 * @param $child
	 * @param $racl
	 * @param $preq
	 * @param $creq
	 * @return unknown_type
	 */
	private static function RQL_relation2SQL($parent, $child, $racl = 1, $preq = false, $creq = false) {
		$rid = rand(1, 999999);

		if (!isset($parent[1])) $parent[1] = $parent[0];
		if (!isset($child[1])) $child[1] = $child[0];
		$prid = $GLOBALS['RQL']['prev_relation_id'];

		$res = Array(
			'f'=>array(),
			'w'=>array(),
			'id'=>0
		);

		$res['f'][1] = "`relations` as `r_$rid`";
		$res['w'][0] = (($prid) ? "`r_$rid`.`parent` = '$parent[0]' AND `r_$rid`.`parent_id` = `r_$prid`.`child_id` AND " : '');
		$res['w'][1] =	"`r_$rid`.`acl` & $racl = $racl AND ";
		$res['w'][2] =	"`r_$rid`.`child` = '$child[0]' ";

			
		if ($preq) {
			$res['f'][0] = "`{$parent[0]}` as `{$parent[1]}`";
			$res['w'][0] = "`r_$rid`.`parent` = '$parent[0]' AND `r_$rid`.`parent_id` = `$parent[1]`.`id` AND ";
		}
			
		if ($creq) {
			$res['f'][2] = "`{$child[0]}` as `{$child[1]}`";
			$res['w'][2] .= "AND `r_$rid`.`child_id` = `$child[1]`.`id` ";
		}

		$res['w'] = implode('', $res['w']);
			
		$res['id'] = $rid;
		$GLOBALS['RQL']['prev_relation_id'] = $rid;

			
		return $res;
	}




	/**
	 * часть парсера RQL
	 * @param $str
	 * @return unknown_type
	 */
	private static function ascheck($str) {
		$res = explode(' as ',  $str);
		for ($x = 0; $x<2; $x++) {
			if (!@$res[$x]) @$res[$x] = $res[0];
			@$res[$x] = trim(@$res[$x]);
		}
			
		return $res;
	}

	static function mk_sql($aq) {
		$query = 'SELECT DISTINCT '.
		((@$aq['select'])?$aq['select']:'*').
				' FROM '.$aq['from'].
		((@$aq['where'])?' WHERE '.$aq['where']:'').
		((@$aq['group'])?' GROUP BY '.$aq['where']:'').
		((@$aq['order'])?' ORDER BY '.$aq['order']:'').
		((@$aq['limit'])?' LIMIT '.$aq['limit']:'');
		return $query;
	}

	/**
	 * Ужадяет запись
	 * @param Record $rec
	 * @return null
	 */
	static function deleteRecord(Record $rec) {
		if (!self::check_conn()) return false;
		$rt = $rec->get_type();
		$id = $rec->id();
		self::removeRecord($rt, $id);
	}

	private static function removeRecord($rt, $id)
	{
		if (!self::check_conn()) return false;
		$childs = self::childs($rt, $id);
			
		$query = "DELETE FROM $rt WHERE id = $id";
		mysqli_query($_SESSION['pconnect'], $query);

		$query = "DELETE FROM `relations` WHERE (`child` = '$rt' AND `id` = '$id') OR (`parent` = '$rt' AND `id` = '$id')";
		mysqli_query($_SESSION['pconnect'], $query);
			
		for($x = 0; $x < count($childs); $x++)
		{
			if ($childs[$x]['child'] == $rt && $childs[$x]['child_id'] == $id) continue;
			if (!self::isAccesible($childs[$x]['child'], $childs[$x]['child_id'])){
				self::removeRecord($childs[$x]['child'], $childs[$x]['child_id']);
			}
		}
	}

	static function isAccesible($rt, $id) {
		if (!self::check_conn()) return false;
		$parents = self::parents($rt, $id);
		return (bool) count($parents);
	}

	static function multiDelete($path, $filter = '', $order = '', $limit = '')
	{
		if (!self::check_conn()) return false;
		$rql = self::RQL2SQL($path, $filter, 64);
		$query = "DELETE ".$rql['sql']['delete']." FROM ".$rql['sql']['from']." WHERE ".$rql['sql']['where'];
		$res = mysqli_query($_SESSION['pconnect'], $query);
		if (!$res) die($query.' ::: '.mysqli_error($_SESSION['pconnect']));
		return true;
	}

	static function check_conn() {
		return $_SESSION['pconnect'] && true;
	}

	/**
	 * Создаёт несуществующего пользователя в ситуации, когда недоступна БД, что позволяет смотреть сохранённые в кеше страницы сайта.
	 * @return null
	 */
	static function fake_user() {
		Log::put('making fakeuser with id:-1 and name '.$_SERVER['REMOTE_ADDR'], 'auth.log');
		$cfg = new Config('fakeuser');
		$usr = $cfg->src();
		$usr['name'] = $_SERVER['REMOTE_ADDR'];
		$usr = new Record($usr, 'user');
		self::$user = $usr;
		$_SESSION['user'] = $usr->id();
	}

	/**
	 * Завершает сессию
	 * @return null
	 */
	static function logout() {
		$c = '';
		setcookie('JMPUID', $c, time()+9999999);
		$_SESSION['user'] = 0;
		self::guest($_SERVER['REMOTE_ADDR']);
		self::fake_user();
	}

	/**
	 * Устанавливает cookie
	 * @return null
	 */
	static function cookie() {
		if (@$_COOKIE['JMPUID']) return;
		if (@$_POST['NO_COOKIE'] == 'true') return;
		$c = md5(time()+rand(0,500));
		$ip = mysqli_real_escape_string($_SESSION['pconnect'], $_SERVER['REMOTE_ADDR']);
		Log::put('Searching cookie records...', 'session.log');
		try {
			$at = DataSource::selectRecord('self,$auth.', 'auth.cookie AND auth.ip = \''.$_SERVER['REMOTE_ADDR'].'\'');
		} catch (Exception $e) {
			Log::put($e->getMessage(), 'session.log');
			$at = DataSource::createRecord('auth', self::$user);
		}
		$at->ip = $_SERVER['REMOTE_ADDR'];
		$at->cookie = $c;
		$at->ipmatch = 1;
		DataSource::saveRecord($at);
		setcookie('JMPUID', $c, time()+9999999);
	}

	/**
	 * Парсер Relations Query Language в SQL.
	 * @param $path
	 * @param $wh
	 * @param $op
	 * @return unknown_type
	 */
	protected static function RQL2SQL($path, $wh = '', $op = 1) {
		if (!stristr($path, 'self')) $path = 'self, '.$path;

		$path = str_replace('self', 'user as self', $path);
		if (trim($wh)) $wh.=' AND ';
		$wh .= 'self.id = '.(int)self::$userid;
		Log::put('PATH:'.print_r($path,1), 'csda.log');
		preg_match_all("/([_a-z0-9]*)\s*\\[([^\\]]*)\\]/", $path, $params);
		$cbr = preg_replace("/([_a-z0-9]*)\s*\\[[^\\]]+\\]/", "\\1", $path);

			
		$rg = "/(>?)\\s*([$]?)([^><;.\\$,]+)\\s*([,;\\.]|<[?&])/";
		preg_match_all($rg, $cbr, $nodes);


		$arr = self::RQL_CBR2ARR($nodes);
		$res = self::RQL_ARR2SQL($arr, $op, $wh);

		$aq['from'] = implode(', ', $res['f']);

		$aq['where'] = (($res['w']) ? '('.$res['w'].') AND ' : '').' ('.$wh.')';
		$aq['select'] = '255 as `###`, `'.@implode('`.*, 255 as `###`, `', $arr['sel']).'`.*';
		$aq['delete'] = '`'.@implode('`, `', $arr['sel']).'`';
		$result['sql'] = $aq;
		$result['types'] = $arr['sel'];

		return $result;
	}

	
	/**
	 * Проверяет, является ли пользователь администратором БД.
	 * @return null
	 */
	static function  grant_root() {
		$user = self::$user;
		try {
			$cercea = self::selectRecord('self, $cercea.');
		} catch (Exception $e) {
			Log::fatal('Unable to delegate admin role to user '.$user->name, QConst::X_USER_NOT_ADMIN);
		}
		if ($cercea->acl < 255) Log::fatal('Unable to delegate admin role to user '.$user->name.' (acl to cercea: '.$cercea->acl.')', QConst::X_USER_NOT_ADMIN);
		Log::put('admin role delegated to user '.$user->name, 'csda.log');
	}
}


?>